package thread;

/**
 * 多线程并发安全问题
 * 当多个线程并发操作同一临界资源，由于线程切换时机不确定，导致执行顺序出现混乱，从而引发不良后果。
 * 临界资源:操作该资源的过程同一时刻只能由一条线程进行。
 */
public class SyncDemo {
    //不能在静态上下文中引用非静态变量,所以创建两个静态变量，
    // 这两个变量是在 main 方法中被赋值和使用的，所以它们必须是 static，
    // 在 static 方法中只能访问 static 成员（变量或方法），不能直接访问非 static 成员
    private static boolean success1 = false;//记录第一个人是否取款成功
    private static boolean success2 = false;//记录第二个人是否取款成功
    public static void main(String[] args) {
        Bank bank = new Bank();
        while(true) {
            //模拟两个人一起取钱
            Thread t1 = new Thread() {
                public void run() {
                    success1 = bank.getMoney(20000);
                }
            };
            Thread t2 = new Thread() {
                public void run() {
                    success2 = bank.getMoney(20000);
                }
            };
            t1.start();
            t2.start();
            try {
                Thread.sleep(20);//卡住20毫秒，目的等待t1和t2执行完毕
            } catch (InterruptedException e) {
            }
            if (success1 & success2) {
                System.out.println("卡BUG成功!!!同时取款成功");
                break;
            } else {
                System.out.println("正常，只有一人取款成功");
                bank.saveAccount(20000);
                success1 = false;
                success2 = false;
            }
        }
    }
}

class Bank{
    private int account=20000;//账号余额2W
    public synchronized boolean getMoney(int money){
        //int account = getAccount(); 的作用是将成员变量 account 的值“复制”一份到局部变量中，
              // 以便后续操作基于一个固定的快照值进行判断和计算，
              // 而不是每次都去访问成员变量。这既符合封装原则，也有助于在并发环境中保持逻辑一致性。

        //为什么要用 getAccount() 来获取值，而不是直接访问成员变量？
        //虽然看起来 getAccount() 只是返回了 account，但它有以下 几个重要作用：

        // 1.封装性（Encapsulation）
               //Java 面向对象编程的核心原则之一就是封装。
               //把成员变量设为 private，并通过 public 方法访问，是为了控制访问方式。
               //未来如果你修改了 account 的存储方式（比如从数据库读取），只需要修改 getAccount() 方法，不需要修改所有调用它的地方。
               //即使现在只是返回 account，但封装是为了未来扩展性.

      // 2️⃣ 线程安全和一致性（在这个例子中尤为重要）
                 //这个程序是一个多线程取款模拟器，用来测试并发问题。
                 //int account = getAccount(); // 获取当前余额副本
                 //这一行代码的作用是：获取当前余额的一个“快照”。
                 //后续所有的判断和计算都基于这个“快照”，而不是每次都去访问成员变量。
                 //这样可以避免在判断过程中成员变量被其他线程修改，造成数据不一致。

        int account = getAccount();  //   //查询账号余额

        if(account>=money){  ////判断余额是否充足
            account = account - money;//修改局部变量的值
            Thread.yield();//让线程在这里主动放弃本次剩余时间片，模拟执行到这里时间耗尽发生线程切换

            saveAccount(account);//重新记账
            return true;//允许出钞
        }
        return false;//拒绝出钞
    }

    public void saveAccount(int account){
        this.account = account;
    }

    public int getAccount(){
        return account;
    }//查询账号余额
}


/*
区分account便于理解
public synchronized boolean getMoney(int money) {
    int currentBalance = getAccount(); // 更清晰的变量名
    if (currentBalance >= money) {
        int newBalance = currentBalance - money;
        Thread.yield();
        saveAccount(newBalance);
        return true;
    }
    return false;
}

public void saveAccount(int newBalance) {
    this.account = newBalance;
}
*/



